package com.webchat.pay.service;


import com.google.common.collect.Lists;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.enums.payment.PaymentOrderDetailStatusEnum;
import com.webchat.common.enums.payment.PaymentOrderStatusEnum;
import com.webchat.common.enums.payment.PaymentTransTypeEnum;
import com.webchat.common.helper.SessionHelper;
import com.webchat.common.service.RedisService;
import com.webchat.common.service.SnowflakeIdGeneratorService;
import com.webchat.common.util.JsonUtil;
import com.webchat.common.util.SpringContextUtil;
import com.webchat.common.util.TransactionSyncManagerUtil;
import com.webchat.domain.dto.payment.CallBackRequestBodyDTO;
import com.webchat.domain.dto.payment.PaymentOrderCreateDTO;
import com.webchat.domain.dto.payment.PaymentOrderDTO;
import com.webchat.domain.dto.payment.PaymentResultDTO;
import com.webchat.domain.dto.payment.PaymentTransRequestDTO;
import com.webchat.domain.dto.payment.PaymentTransferRequestDTO;
import com.webchat.domain.dto.payment.TransIdDTO;
import com.webchat.domain.enums.payment.PaymentResultCodeEnum;
import com.webchat.pay.config.constants.PaymentConstant;
import com.webchat.pay.config.exception.PaymentAccessAuthException;
import com.webchat.pay.repository.dao.IPaymentOrderDAO;
import com.webchat.pay.repository.dao.IPaymentOrderDetailDAO;
import com.webchat.pay.repository.entity.PaymentOrderDetailEntity;
import com.webchat.pay.repository.entity.PaymentOrderEntity;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class PaymentOrderService {

    @Autowired
    private RedisService redisService;
    @Autowired
    private IPaymentOrderDAO paymentOrderDAO;
    @Autowired
    private IPaymentOrderDetailDAO paymentOrderDetailDAO;
    @Autowired
    private PaymentApiService paymentApiService;
    @Autowired
    private SnowflakeIdGeneratorService snowflakeIdGeneratorService;
    @Autowired
    private PaymentCallBackService paymentCallBackService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private AccountService accountService;

    private ReentrantLock lock = new ReentrantLock();

    /**
     * 创建交易订单
     *
     * @param transId
     * @param logId
     * @param paymentOrderCreateDTO
     * @return
     */
    @Transactional
    public String createOrder(String transId,
                              String logId,
                              PaymentOrderCreateDTO paymentOrderCreateDTO) {
        /**
         * transId校验
         */
        TransIdDTO transIdDTO = paymentApiService.getTransIdDTO(transId);
        if (transIdDTO == null ||
                !ObjectUtils.equals(PaymentConstant.TrasnsIdStatusEnum.INIT.getStatus(), transIdDTO.getStatus())) {
            throw new PaymentAccessAuthException("分布式交易事务ID无效！");
        }
        // 接入方应用ID
        Long appId = transIdDTO.getAppId();
        String orderId = this.doCreateOrder(transId, appId, paymentOrderCreateDTO);
        /**
         * 保证事务提交成功后，刷新transId状态 ---> 已经创建订单
         */
        TransactionSyncManagerUtil.registerSynchronization(() -> {
            if (StringUtils.isNotBlank(orderId)) {
                // 刷新事务id状态
                paymentApiService.setPaymentTransIdCache(transId,  PaymentConstant.TrasnsIdStatusEnum.CREATE_ORDER);
                paymentApiService.setPaymentTransOrderInfo(transId,  orderId);
                // 刷新订单缓存
                this.refreshPaymentOrderCache(orderId);
            }
        });
        return orderId;
    }

    /**
     * 支付，支持回调能力
     */
    @Transactional
    public PaymentResultDTO trans(PaymentTransRequestDTO paymentTransRequest) {
        // 1. 完成交易
        PaymentResultDTO paymentResult = doTrans(paymentTransRequest);
        // 2. 回调
        paymentCallBackService.callBack(paymentResult);
        return paymentResult;
    }

    /**
     * 为什么这个方法不加事务？？
     *
     * 答：因为方法实现分两部分：（读操作）基础校验支付参数构造 + （写操作）底层基础支付服务完成
     * 这里“写操作”走代理模式调用com.webchat.pay.service.PaymentOrderService#trans(com.webchat.domain.dto.payment.PaymentTransRequestDTO)
     * trans是有事务的。
     *
     * @param paymentTransferReq
     * @return
     */
    public PaymentResultDTO doTransfer(PaymentTransferRequestDTO paymentTransferReq) {

        // 1. 校验用户支付密码
        boolean validateRes = accountService.validateAccountPassword(paymentTransferReq.getUserId(), paymentTransferReq.getPassword());
        Assert.isTrue(validateRes, "支付密码校验失败");
        // 2. 构造基础支付服务所需参数
        PaymentTransRequestDTO paymentTransRequest = new PaymentTransRequestDTO();
        String paymentOrderId = paymentTransferReq.getOrderId();
        PaymentOrderDTO paymentOrderDTO = getOrder(paymentOrderId);
        BeanUtils.copyProperties(paymentOrderDTO, paymentTransRequest);
        paymentTransRequest.setSourceUserId(paymentOrderDTO.getSourceAccount());
        paymentTransRequest.setTargetUserId(paymentOrderDTO.getTargetAccount());
        paymentTransRequest.setBusinessId(paymentOrderDTO.getBusinessId());
        paymentTransRequest.setBusinessAction(paymentOrderDTO.getBusinessAction());
        // 3. 执行支付 以及支付结果callback
        return SpringContextUtil.getBean(PaymentOrderService.class).trans(paymentTransRequest);
    }


    /**
     * 实际交易流程
     *
     * @param paymentTransRequest
     * @param logId
     * @return
     */
    @Transactional
    public PaymentResultDTO doTrans(PaymentTransRequestDTO paymentTransRequest) {
        String sourceUserId = paymentTransRequest.getSourceUserId();
        PaymentResultDTO paymentResultDTO;
        // 分布式锁保证交易原子性
        String lock = RedisKeyEnum.PAYMENT_PROCESS_LOCK.getKey(sourceUserId);
        RLock rLock = redissonClient.getLock(lock);
        rLock.lock();
        try {
            paymentResultDTO = doPaymentProcess(paymentTransRequest);
        } catch (Exception e) {
            paymentResultDTO = PaymentResultDTO.error(PaymentResultCodeEnum.SERVER_ERR);
        } finally {
            rLock.unlock();
        }
        return setCallBackData(paymentTransRequest, paymentResultDTO);
    }

    private PaymentResultDTO doPaymentProcess(PaymentTransRequestDTO paymentTransRequest) {
        // 校验余额
        String sourceUserId = paymentTransRequest.getSourceUserId();
        BigDecimal amount = paymentTransRequest.getAmount();
        // 校验支付订单
        String orderId = paymentTransRequest.getOrderId();
        PaymentOrderDTO paymentOrder = this.getOrder(orderId);
        if (paymentOrder == null) {
            return PaymentResultDTO.error(PaymentResultCodeEnum.ORDER_NOT_FOUND);
        }
        boolean validateBalanceResult = validateTransBalance(sourceUserId, amount);
        if (!validateBalanceResult) {
            return PaymentResultDTO.error(PaymentResultCodeEnum.BALANCE);
        }
        int transType = paymentTransRequest.getBillType();
        // 交易账户明细
        String paymentId = snowflakeIdGeneratorService.generateId();
        PaymentOrderDetailEntity sourceOrderDetail = new PaymentOrderDetailEntity();
        sourceOrderDetail.setAmount(amount.multiply(new BigDecimal(transType)));
        sourceOrderDetail.setSourceAccount(paymentTransRequest.getSourceUserId());
        sourceOrderDetail.setTargetAccount(PaymentConstant.PAYMENT_SYSTEM_ID);
        sourceOrderDetail.setStatus(PaymentConstant.TrasnsDetailStatusEnum.FINISHED.getStatus());
        sourceOrderDetail.setOrderId(orderId);
        sourceOrderDetail.setPaymentId(paymentId);
        // 托管账户明细
        PaymentOrderDetailEntity targetOrderDetail = new PaymentOrderDetailEntity();
        targetOrderDetail.setAmount(amount.multiply(new BigDecimal(transType * -1)));
        targetOrderDetail.setSourceAccount(PaymentConstant.PAYMENT_SYSTEM_ID);
        targetOrderDetail.setTargetAccount(paymentTransRequest.getSourceUserId());
        targetOrderDetail.setStatus(PaymentConstant.TrasnsDetailStatusEnum.FINISHED.getStatus());
        targetOrderDetail.setOrderId(orderId);
        targetOrderDetail.setPaymentId(paymentId);
        List<PaymentOrderDetailEntity> orderDetails = Lists.newArrayList(sourceOrderDetail, targetOrderDetail);
        /**
         * 交易明细持久化
         */
        paymentOrderDetailDAO.saveAll(orderDetails);
        /**
         * 交易变更，删除缓存+主动刷新保证数据一致性
         */
        this.deleteAccountBalanceCache(sourceOrderDetail.getSourceAccount());
        this.deleteAccountBalanceCache(PaymentConstant.PAYMENT_SYSTEM_ID);
        return PaymentResultDTO.ok();
    }

    private PaymentResultDTO setCallBackData(PaymentTransRequestDTO paymentTransRequest,
                                             PaymentResultDTO paymentResultDTO) {
        String orderId = paymentTransRequest.getOrderId();
        Long appId = Long.valueOf(SessionHelper.getCurrentAppId());
        String tranceId = paymentApiService.getOrderTranceId(orderId);
        CallBackRequestBodyDTO data = new CallBackRequestBodyDTO();
        data.setAppId(appId);
        data.setOrderId(orderId);
        data.setBusinessId(paymentTransRequest.getBusinessId());
        data.setBusinessAction(paymentTransRequest.getBusinessAction());
        data.setAmount(paymentTransRequest.getAmount());
        data.setTranceId(tranceId);
        paymentResultDTO.setData(data);
        return paymentResultDTO;
    }

    /**
     * 校验账户余额
     *
     * @param account
     * @param validateAmount
     * @return
     */
    private boolean validateTransBalance(String account, BigDecimal validateAmount) {

        return true;

//        BigDecimal balance = getAccountBalanceFromCache(account);
//        return balance.compareTo(validateAmount) >= 0;
    }

    /**
     * 查询账户余额
     *
     * @param account
     * @return
     */
    public BigDecimal getAccountBalanceFromCache(String account) {

        String tranceId = SessionHelper.getTranceId();

        String cacheKey = RedisKeyEnum.USER_WALLET_BALANCE_CACHE.getKey();
        String cache = redisService.hget(cacheKey, account);
        if (StringUtils.isNotBlank(cache)) {
            return new BigDecimal(cache);
        }
        return this.refreshAccountBalance(account);
    }


    /**
     * 交易回滚
     * @param transId
     * @param logId
     * @return
     */
    @Transactional
    public boolean rollback(String transId, String logId) {

        /**
         * transId校验
         */
        TransIdDTO transIdDTO = paymentApiService.getTransIdDTO(transId);
        Assert.notNull(transIdDTO, "分布式交易事务ID无效！");
        if (ObjectUtils.equals(PaymentConstant.TrasnsIdStatusEnum.ROLLBACK.getStatus(), transIdDTO.getStatus())) {
            throw new PaymentAccessAuthException("交易已回滚！");
        }
        String orderId = transIdDTO.getOrderId();
        Assert.notNull(orderId, "未查找到订单信息");

        // 回滚订单
        PaymentOrderEntity paymentOrderEntity = paymentOrderDAO.findByOrderId(orderId);
        paymentOrderEntity.setStatus(PaymentOrderStatusEnum.ROLLBACK.getStatus());
        paymentOrderDAO.save(paymentOrderEntity);
        // 回滚交易明细
        List<PaymentOrderDetailEntity> details = paymentOrderDetailDAO.findAllByOrderId(orderId);
        if (CollectionUtils.isEmpty(details)) {
            return true;
        }
        Set<String> refreshAccounts = new HashSet<>();
        for (PaymentOrderDetailEntity detail : details) {
            detail.setStatus(PaymentOrderDetailStatusEnum.ROLLBACK.getStatus());
            refreshAccounts.add(detail.getSourceAccount());
            refreshAccounts.add(detail.getTargetAccount());
        }
        paymentOrderDetailDAO.saveAll(details);
        // 刷新相关账户余额缓存
        this.deleteAccountBalanceCache(new ArrayList<>(refreshAccounts));
        return true;
    }

    /**
     * 刷新账户余额缓存
     *
     * @param account
     * @return
     */
    private BigDecimal refreshAccountBalance(String account) {

        BigDecimal balance = paymentOrderDetailDAO.getAccountBalance(account);
        String cacheKey = RedisKeyEnum.USER_WALLET_BALANCE_CACHE.getKey();
        balance = balance == null ? new BigDecimal("0") : balance;
        redisService.hset(cacheKey, account, balance.toString(), RedisKeyEnum.USER_WALLET_BALANCE_CACHE.getExpireTime());
        return balance;
    }

    /**
     * 账户账户余额缓存
     *
     * @param account
     */
    private void deleteAccountBalanceCache(String account) {
        String cacheKey = RedisKeyEnum.USER_WALLET_BALANCE_CACHE.getKey();
        redisService.hdel(cacheKey, account);
    }

    private void deleteAccountBalanceCache(List<String> accounts) {
        String cacheKey = RedisKeyEnum.USER_WALLET_BALANCE_CACHE.getKey();
        redisService.hmdel(cacheKey, accounts);
    }

    /**
     * 根据订单id获取订单详情
     *
     * @param orderId
     * @return
     */
    public PaymentOrderDTO getOrder(String orderId) {

        String cacheKey = this.cacheKey(orderId);
        String cache = redisService.get(cacheKey);
        if (StringUtils.isNotBlank(cache)) {
            return JsonUtil.fromJson(cache, PaymentOrderDTO.class);
        }
        lock.lock();
        try {
            if (StringUtils.isNotBlank(cache = redisService.get(cacheKey))) {
                return JsonUtil.fromJson(cache, PaymentOrderDTO.class);
            }
            return this.refreshPaymentOrderCache(orderId);
        } catch (Exception e) {
            // TODO
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return null;
    }

    /**
     * 用户查询订单
     *
     * @param orderId
     * @param userId
     * @return
     */
    public PaymentOrderDTO getOrder(String orderId, String userId) {
        Assert.notNull(orderId, "订单信息不存在");
        Assert.notNull(userId, "无订单信息访问权限");
        PaymentOrderDTO order = getOrder(orderId);
        Assert.isTrue(order.getSourceAccount().equals(userId), "无订单信息访问权限");
        return order;
    }

    /**
     * 执行订单创建
     * @param transId
     * @param paymentOrderCreateDTO
     * @return
     */
    public String doCreateOrder(String transId, Long appId, PaymentOrderCreateDTO paymentOrderCreateDTO) {

        // 基于雪花算法+redis原子自增特性，生成分布式下全局唯一订单id
        String orderId = snowflakeIdGeneratorService.generateId();
        // 构造持久化订单entity
        PaymentOrderEntity orderEntity = this.convert(orderId, transId, appId, paymentOrderCreateDTO);
        orderEntity.setStatus(PaymentOrderStatusEnum.NEW.getStatus());
        paymentOrderDAO.save(orderEntity);
        return orderId;
    }

    private PaymentOrderDTO refreshPaymentOrderCache(String orderId) {
        PaymentOrderEntity orderEntity = paymentOrderDAO.findByOrderId(orderId);
        if (orderEntity == null) {
            return null;
        }
        PaymentOrderDTO dto = this.convert(orderEntity);
        String cacheKey = this.cacheKey(orderId);
        redisService.set(cacheKey, JsonUtil.toJsonString(dto), RedisKeyEnum.PAYMENT_ORDER_CACHE.getExpireTime());
        return dto;
    }

    private String cacheKey(String orderId) {
        return  RedisKeyEnum.PAYMENT_ORDER_CACHE.getKey(orderId);
    }

    private PaymentOrderDTO convert(PaymentOrderEntity orderEntity) {
        PaymentOrderDTO dto = new PaymentOrderDTO();
        BeanUtils.copyProperties(orderEntity, dto);
        dto.setCreateTime(orderEntity.getCreateDate().getTime());
        if (orderEntity.getUpdateDate() != null) {
            dto.setUpdateTime(orderEntity.getUpdateDate().getTime());
        }
        return dto;
    }

    private PaymentOrderEntity convert(String orderId, String transId, Long appId,
                                       PaymentOrderCreateDTO paymentOrderCreateDTO) {
        PaymentOrderEntity orderEntity = new PaymentOrderEntity();
        BeanUtils.copyProperties(paymentOrderCreateDTO, orderEntity);
        orderEntity.setOrderId(orderId);
        orderEntity.setTransId(transId);
        orderEntity.setAppId(appId);
        return orderEntity;
    }
}
